
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
#include <pthread.h>

typedef void* wl_ext_http_handle;

//type 0: get file, 1: get data, 2: post
enum {
    WL_EXT_HTTP_GET_FILE = 0,
    WL_EXT_HTTP_GET_DATA,
    WL_EXT_HTTP_POST
};

enum {
    WL_EXT_HTTP_HEADER_STATUS,
    WL_EXT_HTTP_BODY_DATA_STATUS,
    WL_EXT_HTTP_FINISH_STATUS,
    WL_EXT_HTTP_ERROR_STATUS
};

/**
 * @brief http callback
 * 
 * @param handle http handle
 * @param status http status: header, body, finish, error
 * @param pData data
 * @param dataLen data length
 */
typedef void (*wl_ext_http_callback)(wl_ext_http_handle handle, int status, void* pData, unsigned int dataLen);


typedef struct {
    CURL *curl;
    wl_ext_http_callback callback;
    FILE* file;
    void *pContext;
    char *pBody;
    unsigned int bodyLen;
    char *filePath;
    int httpType;
    unsigned int dataLen;
    unsigned char data[0];
}wl_ext_http_handle_impl;



/**
 * @brief init http
 * 
 * @return 0 success,  < 0 fail
 * 
 */
int wl_ext_http_init(void) {
    curl_global_init(CURL_GLOBAL_DEFAULT);
    return 0;
}

/**
 * @brief deinit http
 * 
 */
void wl_ext_http_deinit(void) {
    curl_global_cleanup();
}

/**
 * @brief create http request
 * 
 * @param url url
 * @param method 0: get, 1: post
 * @return http handle
 *  
 */
wl_ext_http_handle wl_ext_http_request_create(const char *url, int type) {
    wl_ext_http_handle_impl *handle = malloc(sizeof(wl_ext_http_handle_impl) + 10240);
    memset(handle, 0, sizeof(wl_ext_http_handle_impl) + 10240);
    handle->curl = curl_easy_init();
    handle->httpType = type;
    
    curl_easy_setopt(handle->curl, CURLOPT_URL, url);

    return handle;
}


static size_t curl_http_get_callback(char *buffer, size_t size, size_t nitems, void *instream) {
    wl_ext_http_handle_impl *handle = (wl_ext_http_handle_impl *)instream;
    if (handle->callback) {
        handle->callback(handle, WL_EXT_HTTP_BODY_DATA_STATUS, buffer, size * nitems);
    }
    return size * nitems;
}



static void* curl_http_get_task(void *instream) {
    wl_ext_http_handle_impl *handle = (wl_ext_http_handle_impl *)instream;

    CURLcode res = curl_easy_perform(handle->curl);
    if (res != CURLE_OK) {
        wl_ext_http_callback callback = handle->callback;
        handle->callback(handle, WL_EXT_HTTP_ERROR_STATUS, NULL, 0);
        return NULL;
    }
    
    curl_easy_cleanup(handle->curl);

    wl_ext_http_callback callback = handle->callback;
    handle->callback(handle, WL_EXT_HTTP_FINISH_STATUS, handle->data, handle->dataLen);

    return NULL;
}

/**
 * @brief http get data. the data will be supplied in callback
 * 
 * @param handle http handle
 * @param callback callback
 * @param pContext user context
 *  
 * @return 0 success, < 0 fail
 */
static int curl_debug(CURL *curl, curl_infotype type, char *data, size_t size, void *userp) {
    // printf("[debug]:------------------->\n%s\n-------------------------end\n", data);
    return 0;
}


int wl_ext_http_get_data(wl_ext_http_handle handle, wl_ext_http_callback callback, void *pContext) {
    wl_ext_http_handle_impl *handle_impl = (wl_ext_http_handle_impl *)handle;

    handle_impl->callback = callback;
    handle_impl->pContext = pContext;

    curl_easy_setopt(handle_impl->curl, CURLOPT_WRITEFUNCTION, curl_http_get_callback);
    curl_easy_setopt(handle_impl->curl, CURLOPT_WRITEDATA, handle_impl);
    curl_easy_setopt(handle_impl->curl, CURLOPT_VERBOSE, 1L);
    curl_easy_setopt(handle_impl->curl, CURLOPT_DEBUGFUNCTION, curl_debug);     

    pthread_t thread;
    int ret = pthread_create(&thread, NULL, curl_http_get_task, handle_impl);
    if (ret != 0) {
        return -1;
    }
    pthread_detach(thread);


    return 0;
}


static size_t curl_http_get_file_callback(char *buffer, size_t size, size_t nitems, void *instream) {
    wl_ext_http_handle_impl *handle = (wl_ext_http_handle_impl *)instream;
    if (handle->callback) {
        handle->callback(handle, WL_EXT_HTTP_BODY_DATA_STATUS, buffer, size * nitems);
    }
    return size * nitems;
}



void* curl_http_get_file_task(void *instream) {
    wl_ext_http_handle_impl *handle = (wl_ext_http_handle_impl *)instream;

    CURLcode res = curl_easy_perform(handle->curl);
    if (res != CURLE_OK) {
        wl_ext_http_callback callback = handle->callback;
        handle->callback(handle, WL_EXT_HTTP_ERROR_STATUS, NULL, 0);
        return NULL;
    }

    curl_easy_cleanup(handle->curl);
    wl_ext_http_callback callback = handle->callback;
    handle->callback(handle, WL_EXT_HTTP_FINISH_STATUS, handle->data, handle->dataLen);
    fclose(handle->file);
    return NULL;
}
/**
 * @brief http get. the data will be saved with filename supplied, and return the file path in callback
 * with param filePath
 * 
 * @param handle http handle
 * @param filePath file path, if file is null, should generate a file name
 * @param callback callback
 * @param pContext user context
 *  
 * @return 0 success, < 0 fail
 */
int wl_ext_http_get_file(wl_ext_http_handle handle, const char *filePath, wl_ext_http_callback callback, void *pContext) {
    wl_ext_http_handle_impl *handle_impl = (wl_ext_http_handle_impl *)handle;

    handle_impl->callback = callback;
    handle_impl->pContext = pContext;
    handle_impl->file = fopen(filePath, "wb");

    curl_easy_setopt(handle_impl->curl, CURLOPT_WRITEFUNCTION, curl_http_get_file_callback);
    curl_easy_setopt(handle_impl->curl, CURLOPT_WRITEDATA, handle_impl);
    curl_easy_setopt(handle_impl->curl, CURLOPT_VERBOSE, 1L);
    curl_easy_setopt(handle_impl->curl, CURLOPT_DEBUGFUNCTION, curl_debug);

    pthread_t thread;
    int ret = pthread_create(&thread, NULL, curl_http_get_task, handle_impl);
    if (ret != 0) {
        return -1;
    }
    pthread_detach(thread);
    return 0;
}

static size_t curl_http_post_callback(char *buffer, size_t size, size_t nitems, void *instream) {
    wl_ext_http_handle_impl *handle = (wl_ext_http_handle_impl *)instream;
    if (handle->callback) {
        handle->callback(handle, WL_EXT_HTTP_BODY_DATA_STATUS, buffer, size * nitems);
    }
    return size * nitems;
}


static void* curl_http_post_data_task(void *instream) {
    wl_ext_http_handle_impl *handle = (wl_ext_http_handle_impl *)instream;

    CURLcode res = curl_easy_perform(handle->curl);
    if (res != CURLE_OK) {
        wl_ext_http_callback callback = handle->callback;
        handle->callback(handle, WL_EXT_HTTP_ERROR_STATUS, NULL, 0);
        return NULL;
    }

    curl_easy_cleanup(handle->curl);

    wl_ext_http_callback callback = handle->callback;
    handle->callback(handle, WL_EXT_HTTP_FINISH_STATUS, handle->data, handle->dataLen);
    return NULL;    
}
/**
 * @brief http post data. the result data will be supplied in callback
 * 
 * @param handle http handle
 * @param pBody pBody data, it should be string in json format
 * @param bodyLen body length
 * @param callback callback
 * @param pContext user context
 *  
 * @return 0 success, < 0 fail
 */
int wl_ext_http_post_data(wl_ext_http_handle handle, const char *pBody, unsigned int bodyLen, wl_ext_http_callback callback, void *pContext) {
    wl_ext_http_handle_impl *handle_impl = (wl_ext_http_handle_impl *)handle;
    struct curl_slist *headers = NULL;

    handle_impl->callback = callback;
    handle_impl->pContext = pContext;

    headers = curl_slist_append(headers, "Content: application/json");
    curl_easy_setopt(handle_impl->curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(handle_impl->curl, CURLOPT_POSTFIELDS, pBody);
    curl_easy_setopt(handle_impl->curl, CURLOPT_WRITEFUNCTION, curl_http_post_callback);
    curl_easy_setopt(handle_impl->curl, CURLOPT_WRITEDATA, handle_impl);
    curl_easy_setopt(handle_impl->curl, CURLOPT_VERBOSE, 1L);
    curl_easy_setopt(handle_impl->curl, CURLOPT_DEBUGFUNCTION, curl_debug);    
    
    pthread_t thread;
    int ret = pthread_create(&thread, NULL, curl_http_post_data_task, handle_impl);
    if (ret != 0) {
        return -1;
    }
    pthread_detach(thread); 
    
    return 0;
}
/**
 * @brief http set header
 * 
 * @param handle http handle
 * @param key header key
 * @param value header value
 * 
 * @return 0 success, < 0 fail
 *  
 */
int wl_ext_http_set_header(wl_ext_http_handle handle, const char *key, const char *value) {
    wl_ext_http_handle_impl *handle_impl = (wl_ext_http_handle_impl *)handle;
    struct curl_slist *headers = NULL;
    char header[256];
    snprintf(header, sizeof(header), "%s: %s", key, value);

    headers = curl_slist_append(headers, header);
    if (!headers) {
        return -1;
    }
    curl_easy_setopt(handle_impl->curl, CURLOPT_HTTPHEADER, headers);

    return 0;
}

/**
 * @brief http post file. the result data will be supplied in callback
 * 
 * @param handle http handle
 *
 */
void wl_ext_http_close(wl_ext_http_handle handle) {
    wl_ext_http_handle_impl *handle_impl = (wl_ext_http_handle_impl *)handle;
    if (handle_impl == NULL) {
        return;
    }
    free(handle_impl);
}

/**
 * @brief http get context
 * 
 * @param handle http handle
 * @return user context
 *  
 */
void* wl_ext_http_get_context(wl_ext_http_handle handle) {
    wl_ext_http_handle_impl *handleimpl = (wl_ext_http_handle_impl *)handle;
    if (handleimpl == NULL) {
        return NULL;
    }
    return handleimpl->pContext;   
}

/**
 * @brief http get type
 * 
 * @param handle http handle
 * @return 0: get file, 1: get data, 2: post
 *  
 */
int wl_ext_http_get_type(wl_ext_http_handle handle){
    wl_ext_http_handle_impl *handleimpl = (wl_ext_http_handle_impl *)handle;
    if (handleimpl == NULL) {
        return 0;
    }
    return handleimpl->httpType;   
}
